package com.theaf.core.repo.specification;

import com.theaf.core.constant.SymbolConstant;
import com.theaf.core.utils.SmExceptionHelper;
import lombok.Data;
import org.hibernate.jpa.criteria.CriteriaBuilderImpl;
import org.hibernate.jpa.criteria.expression.LiteralExpression;

import javax.persistence.criteria.*;
import java.util.*;

/**
 * @Author:songwenke
 * @Description:操作符类，这个类中存储了键值对和操作符号，另外存储了连接下一个条件的类型是and还是or
 * 创建时通过 id>=7,其中id就是key,>=就是oper操作符，7就是value
 * 特殊的自定义几个操作符(:表示like %v%，b:表示v%,:b表示%v)
 * @Date:create in 8:40 2017/12/11
 * @Modified By:
 **/
@Data
public class SqlOperator<T> {
    public enum OPER {
        EQ("等于"), NE("不等于"),GT("大于"), GE("大于等于"), LT("小于"),LEA("小于等于,如为时间，要加一天"),LE("小于等于"),
        LIKE("像"),LIKES("像，但在值前后加上分割号"),NLIKE("不像"),LLIKE("左像"),
        LIKER("右像"),NLLIKE("不左像"),NLIKER("不右像"),NULL("空"),NNULL("非空"),
        IN("在什么范围内"),NIN("在什么范围内"),BETWEEN("在什么之间"),
        NOBETWEEN("不在什么之间"),EXISTS("存在子查询"),NOEXISTS("不存在子查询");
        private String title;
        private  OPER(String title){
            this.title = title;
        }
    }
    public enum JOIN {
        AND,OR
    }
    /**
     * 操作符的key，如查询时的name,id之类,支持函数
     */
    private Object key;
    /**
     * 操作符的value，具体要查询的值
     */
    private Object value;
    /**
     * 操作符，自己定义的一组操作符，用来方便查询
     */
    private OPER oper;

    /**
     * 用于子查询时的父查询对象的引用
     */
    private Root parentRoot;

    protected String splitSymbol  = SymbolConstant.COMMA;
    /**
     * 连接的方式：and或者or
     */
    private JOIN join;

    /**
     * 表达式处理
     * @param root
     * @param cb
     * @param v
     * @return
     */
    private Object expressHandler(Root<T> root,CriteriaBuilder cb,Object v){
        if(v instanceof SubqueryEx){
            SubqueryEx subqueryEx = (SubqueryEx)v;
            Subquery subquery = subqueryEx.getSubquery();
            SqlBuilder sqlBuilder = subqueryEx.getSqlBuilder();
            sqlBuilder.setParentRoot(root);
            return subquery.where(sqlBuilder.generatePredicate((Root)subquery.getRoots().iterator().next(),cb));
        }else
        if(v instanceof SqlValue){
            return new LiteralExpression((CriteriaBuilderImpl)cb, ((SqlValue)v).getValue());
        }else if(v instanceof String) {
            //是否带函数操作
            if(((String) v).indexOf(SymbolConstant.LEFT_SMALL_BRACKET)!=-1&&((String) v).indexOf(SymbolConstant.RIGHT_SMALL_BRACKET)!=-1){
                String fun = ((String) v).substring(0,((String) v).indexOf(SymbolConstant.LEFT_SMALL_BRACKET));
                String field = ((String) v).substring(((String) v).indexOf(SymbolConstant.LEFT_SMALL_BRACKET)+1,
                        ((String) v).lastIndexOf(SymbolConstant.RIGHT_SMALL_BRACKET));
                try {
                    return cb.function(fun,root.getJavaType().getDeclaredField(field).getType(),getJoin(root,field));
                } catch (NoSuchFieldException e) {
                    SmExceptionHelper.sysCheckException(field+"属性找不到！");
                }
            }
            return getJoin(root,(String)v);
        }else{
            return v;
        }
    }
    private static ThreadLocal threadLocal = new ThreadLocal();
    public static void clearRootCache(){
        threadLocal.remove();
    }
    public Expression getJoin(Root<T> root,String ex){
        From currentForm = root;
        //引用父查询的路径
        if(ex.startsWith("@")&&parentRoot!=null){
            currentForm = parentRoot;
            ex = ex.substring(1);
        }
        Map map = (Map)threadLocal.get();
        if(map == null){
            map = new HashMap();
            threadLocal.set(map);
        }
        if(ex.indexOf(SymbolConstant.PIONT)!=-1){
            String[] exs = ex.split("\\.");
            From rev = currentForm;
            String pre = null;
            for(int i=0;i<exs.length-1;i++) {
                pre = (pre==null?"":(pre+"."))+exs[i];
                Join joinRoot = (Join)map.get(pre);
                if(joinRoot==null) {
                    joinRoot = rev.join(exs[i],JoinType.LEFT);
                    map.put(pre,joinRoot);
                }
                rev = joinRoot;
            }
            return rev.get(exs[exs.length-1]);
        }else{
            return root.get(ex);
        }
    }

    public Predicate generatePredicate(Root<T> root, CriteriaBuilder criteriaBuilder) {
        /* 根据不同的操作符返回特定的查询*/
        if((value==null||(SqlValue.class.isInstance(value)&&((SqlValue)value).isNull())
             ||(Collection.class.isInstance(value)&&((Collection)value).size()==0)
                ||value.getClass().isArray()&&((Object[])value).length==0)
                &&!this.getOper().equals(SqlOperator.OPER.NULL)
                &&!this.getOper().equals(OPER.NNULL)&&!this.getOper().equals(OPER.EXISTS)){
            return null;
        }
        if(this.getKey()==null){
            throw SmExceptionHelper.sysCheckException("key不能为空");
        }
        Object right = expressHandler(root,criteriaBuilder,value);
        Object left = expressHandler(root,criteriaBuilder,key);
        if(this.getOper().equals(SqlOperator.OPER.EQ)) {//=
            if(Expression.class.isInstance(right)) {
                return criteriaBuilder.equal((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.equal((Expression) left,  (Object)right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.GE)) {//>=
            if(Expression.class.isInstance(right)) {
                return criteriaBuilder.greaterThanOrEqualTo((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.greaterThanOrEqualTo((Expression) left,  (Comparable) right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.LEA)) {//<=
            if(value instanceof Date){
                Calendar calendar = Calendar.getInstance();
                calendar.setTime((Date)value);
                calendar.set(Calendar.HOUR_OF_DAY,23);
                calendar.set(Calendar.MINUTE,59);
                calendar.set(Calendar.SECOND,59);
                calendar.set(Calendar.MILLISECOND,999);
                return criteriaBuilder.lessThanOrEqualTo((Expression) left,  (Comparable) calendar.getTime());
            }else  if(Expression.class.isInstance(right)) {
                return criteriaBuilder.lessThanOrEqualTo((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.lessThanOrEqualTo((Expression) left,  (Comparable) right);
            }
        }else if(this.getOper().equals(SqlOperator.OPER.LE)) {
            if(Expression.class.isInstance(right)) {
                return criteriaBuilder.lessThanOrEqualTo((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.lessThanOrEqualTo((Expression) left,  (Comparable) right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.GT)) {
            if(Expression.class.isInstance(right)) {
                return criteriaBuilder.greaterThan((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.greaterThan((Expression) left,  (Comparable) right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.LT)) {
            if(Expression.class.isInstance(right)) {
                return criteriaBuilder.lessThan((Expression) left, (Expression) right);
            }else{
                return criteriaBuilder.lessThan((Expression) left,  (Comparable) right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.LIKE)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,SymbolConstant.SIGN+((SqlValue) value).getValue()+SymbolConstant.SIGN);
            }
            return criteriaBuilder.like((Expression) left,
                    (criteriaBuilder.concat(SymbolConstant.SIGN, criteriaBuilder.concat((Expression) right, SymbolConstant.SIGN))));
        } else if(this.getOper().equals(SqlOperator.OPER.LIKES)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,
                        SymbolConstant.SIGN+splitSymbol+((SqlValue) value).getValue()+splitSymbol+SymbolConstant.SIGN);
            }
            return criteriaBuilder.like((Expression) left,
                    (criteriaBuilder.concat(SymbolConstant.SIGN+splitSymbol, criteriaBuilder.concat((Expression) right, splitSymbol+SymbolConstant.SIGN))));
        } else if(this.getOper().equals(SqlOperator.OPER.NLIKE)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,
                        SymbolConstant.SIGN+((SqlValue) value).getValue()+SymbolConstant.SIGN).not();
            }
            return criteriaBuilder.like((Expression) left,
                    (criteriaBuilder.concat(SymbolConstant.SIGN, criteriaBuilder.concat((Expression) right, SymbolConstant.SIGN)))).not();
        } else if(this.getOper().equals(SqlOperator.OPER.LIKER)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,((SqlValue) value).getValue()+SymbolConstant.SIGN);
            }
            return criteriaBuilder.like((Expression) left,
                    criteriaBuilder.concat((Expression) right, SymbolConstant.SIGN));
        } else if(this.getOper().equals(SqlOperator.OPER.LLIKE)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,SymbolConstant.SIGN+((SqlValue) value).getValue());
            }
            return criteriaBuilder.like((Expression) left,(criteriaBuilder.concat(SymbolConstant.SIGN, (Expression) right)));
        }else if(this.getOper().equals(SqlOperator.OPER.NLIKER)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,((SqlValue) value).getValue()+SymbolConstant.SIGN).not();
            }
            return criteriaBuilder.like((Expression) left,
                    criteriaBuilder.concat((Expression) right, SymbolConstant.SIGN)).not();
        } else if(this.getOper().equals(SqlOperator.OPER.NLLIKE)) {
            if(SqlValue.class.isInstance(value)){
                return criteriaBuilder.like((Expression) left,SymbolConstant.SIGN+((SqlValue) value).getValue()).not();
            }
            return criteriaBuilder.like((Expression) left,(criteriaBuilder.concat(SymbolConstant.SIGN, (Expression) right))).not();
        } else if(this.getOper().equals(SqlOperator.OPER.NULL)) {
            return criteriaBuilder.isNull((Expression)left);
        } else if(this.getOper().equals(SqlOperator.OPER.NNULL)) {
            return criteriaBuilder.isNotNull((Expression)left);
        } else if(this.getOper().equals(SqlOperator.OPER.NE)) {
            if(Expression.class.isInstance(right)){
                return criteriaBuilder.notEqual((Expression) left, (Expression) value);
            }else{
                return criteriaBuilder.notEqual((Expression) left,  (Object)right);
            }
        } else if(this.getOper().equals(SqlOperator.OPER.IN)) {
            if(Expression.class.isInstance(right)){
                return ((Expression) left).in((Expression<?>) right);
            }else if(Collection.class.isAssignableFrom(right.getClass())){
                return ((Expression) left).in((Collection<?>)value);
            }else {
                return ((Expression) left).in((Object[])((SqlValue) value).getValue());
            }
        } else if(this.getOper().equals(SqlOperator.OPER.NIN)) {
            if(Expression.class.isInstance(right)){
                return ((Expression) left).in((Expression<?>) right).not();
            }else if(Collection.class.isAssignableFrom(right.getClass())){
                return ((Expression) left).in((Collection<?>)value).not();
            }else {
                return ((Expression) left).in((Object[])((SqlValue) value).getValue()).not();
            }
        } else if(this.getOper().equals(OPER.BETWEEN)) {
            if(value.getClass().isArray()&&((Object[])value).length==2){
                Object[] obs = (Object[])((SqlValue) value).getValue();
                return criteriaBuilder.between((Expression)left,
                        (Expression)expressHandler(root,criteriaBuilder,obs[0]),(Expression)expressHandler(root,criteriaBuilder,obs[1]));
            }else if(value instanceof Collection&&((Collection)value).size()==2){
                Iterable iterable = (Iterable)this.getValue();
                Iterator it = iterable.iterator();
                return criteriaBuilder.between((Expression)left,
                        (Expression)expressHandler(root,criteriaBuilder,it.next()),(Expression)expressHandler(root,criteriaBuilder,it.next()));
            }
        }else if(this.getOper().equals(OPER.NOBETWEEN)) {
            if(value.getClass().isArray()&&((Object[])value).length==2){
                Object[] obs = (Object[])((SqlValue) value).getValue();
                return criteriaBuilder.between((Expression)left,
                        (Expression)expressHandler(root,criteriaBuilder,obs[0]),(Expression)expressHandler(root,criteriaBuilder,obs[1])).not();
            }else if(value instanceof Collection&&((Collection)value).size()==2){
                Iterable iterable = (Iterable)this.getValue();
                Iterator it = iterable.iterator();
                return criteriaBuilder.between((Expression)left,
                        (Expression)expressHandler(root,criteriaBuilder,it.next()),(Expression)expressHandler(root,criteriaBuilder,it.next())).not();
            }
        }else if(this.getOper().equals(OPER.EXISTS)){
            return criteriaBuilder.exists((Subquery)left);
        } else if(this.getOper().equals(OPER.NOEXISTS)){
            return criteriaBuilder.exists((Subquery)left).not();
        }
        return null;
    }
}
